#include "renderer_directx9.h"
#include "vertexbuffer_directx9.h"
#include "indexbuffer_directx9.h"
#include "shader_directx9.h"
#include "system/window.h"
#include <d3dx9math.h>

namespace gecko
{

void Renderer_DirectX9::Clear()
{
	m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, m_clearColour, 1.0f, 0 );
}

void Renderer_DirectX9::SetClearColour( const Colour& colour )
{
	m_clearColour = colour.ToInt();
}

bool Renderer_DirectX9::BeginScene()
{
	m_pd3dDevice->BeginScene();
	return true;
}

bool Renderer_DirectX9::EndScene()
{
	m_pd3dDevice->EndScene();
	return true;
}

bool Renderer_DirectX9::Present()
{
	m_pd3dDevice->Present( NULL, NULL, NULL, NULL );
	return true;
}

bool Renderer_DirectX9::Destroy()
{
#if defined(_DEBUG) || defined(_PROFILE)
	//			DebugLines::instance()->destroy();
#endif

	if( m_pd3dDevice != NULL)
	{
		m_pd3dDevice->Release();
		m_pd3dDevice = NULL;
	}

	if( m_pD3D != NULL)
	{
		m_pD3D->Release();
		m_pD3D = NULL;
	}

	//m_vertexBuffers.clear();
	//m_indexBuffers.clear();
	//m_shaders.clear();
	// 
	// 			for ( FontList::Iterator fontItr = m_fonts.begin(); fontItr != m_fonts.end(); ++fontItr )
	// 			{
	// 				delete (*fontItr);
	// 			}
	// 
	// 			m_fonts.destroy();

	//GT_SAFE_DELETE(m_frustrum);

	//m_isAlive = false;

	DebugPrint("D3D9 renderer destroyed");
	return true;
}

bool Renderer_DirectX9::Initialise( int width, int height, int /*bpp*/, bool fullscreen )
{
#pragma message(" * * * IMPORTANT!!! Do some proper device cap enumeration here... * * * ")
	m_iViewportWidth = width;
	m_iViewportHeight = height;

	m_width = width;
	m_height = height;

	DebugPrint("Initialising D3D9 renderer");

	if ( NULL == ( m_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
	{
		DebugPrint("D3D9 renderer initialisation failed");
		return false;
	}

	if ( !EnumerateDisplayModes() )
		return false;

	GeneratePresentParameters(width, height, fullscreen);

	D3DCAPS9 devCaps; 
	ZeroMemory(&devCaps, sizeof(D3DCAPS9));
	m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &devCaps);

	DWORD behaviourFlags = 0;

	if ( devCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
		behaviourFlags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else behaviourFlags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	behaviourFlags |= D3DCREATE_MULTITHREADED;

	//if ( devCaps.DevCaps & D3DDEVCAPS_PUREDEVICE && 
	//     behaviourFlags & D3DCREATE_HARDWARE_VERTEXPROCESSING )
	//     behaviourFlags |= D3DCREATE_PUREDEVICE;

	DebugPrint("Creating D3D9 device");

	if( FAILED( m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hwnd,
		behaviourFlags,
		&m_d3dpp, &m_pd3dDevice) ) )
	{
		DebugPrint("D3D9 creation failed");
		return false;
	}

	m_pd3dDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
	m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
	m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CW);
	m_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);
	m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
	m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
	//m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1,D3DTA_DIFFUSE);
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
	m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

	m_pd3dDevice->SetRenderState(D3DRS_COLORVERTEX, TRUE);
	m_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, TRUE);
	m_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);

	D3DVIEWPORT9 viewData = { 0, 0, width, height, 0.0f, 1.0f };
	m_pd3dDevice->SetViewport(&viewData);

	//setRendermode(RM_WIREFRAME);

	//m_pd3dDevice->SetRenderState( D3DRS_AMBIENT, D3DCOLOR_ARGB(255, 255, 255, 255));

	//m_frustrum = new Frustum();

	//m_isAlive = true;

#if defined(_DEBUG) || defined(_PROFILE)
	//DebugLines::Instance()->Init(this);
#endif

	//SetCamera(Math::Vector3f(0.0f, 0.0f, -100.0f), Math::Vector3f(), Math::Vector3f(0.0f, 1.0f, 0.0f), 45.0f);

	SetClearColour(Colour(0.2f, 0.3f, 0.4f, 1.0f));

	m_VertexBatcher.Initialise(this);

	return true;
}

bool Renderer_DirectX9::SetCamera(const Vector3f& from, const Vector3f& at, const Vector3f& up, float fov)
{
	m_cameraFrom = from;
	m_cameraAt   = at;
	m_cameraUp   = up;
	m_cameraFOV  = fov;

	D3DXMATRIX matView;
	D3DXVECTOR3 d3dFrom(from.x, from.y, from.z);
	D3DXVECTOR3 d3dAt  (at.x, at.y, at.z);
	D3DXVECTOR3 d3dUp  (up.x, up.y, up.z);
	D3DXMatrixLookAtRH(&matView, &d3dFrom, &d3dAt, &d3dUp);

	D3DXMATRIX matPerspective;
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);
	D3DXMatrixPerspectiveFovRH(&matPerspective,
		D3DXToRadian(fov),
		(float)m_iViewportWidth/(float)m_iViewportHeight,
		5.0f,
		5000.0f);

	m_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);
	m_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
	m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matPerspective);

	m_viewMatrix = (Matrix44&)*matView;
	m_projectionMatrix = (Matrix44&)*matPerspective;

	//m_frustrum->CalcClippingPlanes(m_viewMatrix * m_projMatrix);

	return true;
}

bool Renderer_DirectX9::SetOrthoProjection( int /*width*/, int /*height*/ )
{
	return true;
}

bool Renderer_DirectX9::SetPerspectiveProjection( float fov, int width, int height, double nearClip, double farClip )
{
	Vector3f cameraFrom(0.0f, 0.0f, 0.0f);
	Vector3f cameraAt(0.0f, 0.0f, -1.0f);
	Vector3f cameraUp(0.0f, 1.0f, 0.0f);

	D3DXMATRIX matView;
	D3DXVECTOR3 d3dFrom(cameraFrom.x, cameraFrom.y, cameraFrom.z);
	D3DXVECTOR3 d3dAt  (cameraAt.x, cameraAt.y, cameraAt.z);
	D3DXVECTOR3 d3dUp  (cameraUp.x, cameraUp.y, cameraUp.z);
	D3DXMatrixLookAtRH(&matView, &d3dFrom, &d3dAt, &d3dUp);

	D3DXMATRIX matPerspective;
	D3DXMATRIX matWorld;
	D3DXMatrixIdentity(&matWorld);
	D3DXMatrixPerspectiveFovRH(&matPerspective, D3DXToRadian(fov), (float)width/(float)height, (float)nearClip, (float)farClip);

	m_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);
	m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matPerspective);

	m_viewMatrix = (Matrix44&)*matView;
	m_projectionMatrix = (Matrix44&)*matPerspective;

	return true;
}

IVertexBuffer* Renderer_DirectX9::CreateVertexBuffer( unsigned int vertexCount, bool dynamic )
{
	VertexBuffer_DirectX9* vb = new VertexBuffer_DirectX9;
	vb->Initialise(this, vertexCount, dynamic);
	return vb;
}

IIndexBuffer* Renderer_DirectX9::CreateIndexBuffer( unsigned int indexCount, bool dynamic )
{
	IndexBuffer_DirectX9* ib = new IndexBuffer_DirectX9;
	ib->Initialise(this, indexCount, dynamic);
	return ib;
}

bool Renderer_DirectX9::EnumerateDisplayModes()
{
	DebugPrint("Enumerating display modes");

	int nMaxAdaptorModes = m_pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8 );
	D3DDISPLAYMODE d3ddm;
	int numDisplayModes = 0;
	for( int nMode = 0; nMode < nMaxAdaptorModes; ++nMode )
	{
		if( FAILED(m_pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8, nMode, &d3ddm)) )
		{
			return false;
		}

		if ( d3ddm.Width < 640 || d3ddm.Height < 480 )
			continue;

		if ( d3ddm.Format != D3DFMT_X8R8G8B8 )
			continue;

		SDisplayMode mode;
		mode.m_width = d3ddm.Width;
		mode.m_height = d3ddm.Height;

		bool bModeFound = false;
		for ( int i = 0; i < numDisplayModes; i++ )
		{
			if ( mode.m_width == m_displayModes[i].m_width && mode.m_height == m_displayModes[i].m_height )
				bModeFound = true;
		}
		if ( bModeFound )
			continue;

		m_displayModes[numDisplayModes++] = mode;

		DebugPrint("display mode found (%dx%d)", mode.m_width, mode.m_height);
	}

	return true;
}

void Renderer_DirectX9::GeneratePresentParameters( int width, int height, bool fullscreen )
{
	ZeroMemory( &m_d3dpp, sizeof(m_d3dpp) );

	if ( fullscreen )
	{
		m_d3dpp.Windowed          = FALSE;
		m_d3dpp.BackBufferWidth   = width;
		m_d3dpp.BackBufferHeight  = height;
		m_d3dpp.BackBufferFormat  = D3DFMT_X8R8G8B8; 
		m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
		//d3dpp.Flags = displayMode.Format;
	}
	else
	{
		m_d3dpp.Windowed          = TRUE;
		m_d3dpp.BackBufferWidth   = width;
		m_d3dpp.BackBufferHeight  = height;
		m_d3dpp.BackBufferFormat  = D3DFMT_UNKNOWN; 
		m_d3dpp.BackBufferCount = 1;
		m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		m_d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
		m_d3dpp.FullScreen_RefreshRateInHz = 0;
	}

	// TODO - implement user-controlled quality options
#if 0
	m_d3dpp.MultiSampleType = D3DMULTISAMPLE_4_SAMPLES;
#endif

	m_d3dpp.EnableAutoDepthStencil = TRUE;
	m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
}

LPDIRECT3DDEVICE9 Renderer_DirectX9::GetD3DDevice()
{
	return m_pd3dDevice;
}

IShader* Renderer_DirectX9::CreateShader( const char* filename, unsigned int numParameters )
{
	Shader_DirectX9* shader = new Shader_DirectX9(numParameters);
	if (shader->Serialise(this, filename))
	{
		return shader;
	}
	else
	{
		delete shader;
		return NULL;
	}
}

void Renderer_DirectX9::SetRenderMode( eRendermode renderMode )
{
	switch (renderMode)
	{
	case RM_POINT:
		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_POINT);
		break;
	case RM_WIREFRAME:
		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);
		break;
	case RM_SOLID:
		m_pd3dDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID);
		break;
	}
}

}
